如果要說一個網站最單純的是什麼,應該就是所謂的 「View」 吧,擺上幾個文字 + 幾張圖片,丟到 Server 上就是一個網站,甚至打開記事本就可以編輯網站內容(現在也是可以),既然是這麼重要的部分,那怎麼可以缺少呢?昨天我們已經做出 Controller,今天就接著把 View 完成,讓整個框架更靈活有趣吧
如果你寫過 Rails 一段時間,應該很熟悉 Rails 預設用來產生 Template 的方式是 Embedded Ruby(ERb)
例如像是這樣
<h1><%= Time.now.to_s %></h1>
這種語法讓我們很容易的在 HTML 裡面穿插程式碼,所以今天要做得事情,也會跟 Rails 一樣,用 ERB 當作我們的預設 Template,另外在Rails 5.1 版之後,View 的 templete 已經從 erubis
換成 erubi
erubi is included in Rails 5.1, replacing erubis,所以我們這邊就直接使用 erubi
首先打開 mavericks.gemspec
,我們在這個檔案最底下加上 erubi
# mavericks/mavericks.gemspec
spec.add_runtime_dependency "rack"
# 加上 erubi
spec.add_runtime_dependency "erubi"
接著在 lib/mavericks.rb
這隻檔案裡面,加上 require "mavericks/controller"
,並且把原本底下的 class Controller
給移除,因為接下來 Controller 程式碼會越來越多,勢必要做分離,整理後的程式碼如下
# mavericks/lib/mavericks.rb
require "mavericks/version"
require "mavericks/routing"
require "mavericks/support"
require "mavericks/dependencies"
# 加上這段
require "mavericks/controller"
module Mavericks
class Error < StandardError; end
class Application
def call(env)
return favicon if env["PATH_INFO"] == '/favicon.ico'
return index(env) if env["PATH_INFO"] == '/'
begin
klass, act = get_controller_and_action(env)
controller = klass.new(env)
text = controller.send(act)
[200, {'Content-Type' => 'text/html'},
[text]]
rescue
[404, {'Content-Type' => 'text/html'},
['This is a 404 page!!']]
end
end
private
def index(env)
begin
home_klass = Object.const_get('HomeController')
controller = home_klass.new(env)
text = controller.send(:index)
[200, {'Content-Type' => 'text/html'}, [text]]
rescue NameError
[200, {'Content-Type' => 'text/html'}, ['This is a index page']]
end
end
def favicon
return [404, {'Content-Type' => 'text/html'}, []]
end
end
# 移除
# class Controller
# .
# .
# end
end
接著新增一個 controller.rb
檔案,將搬移過來的 class Controller
貼上,並且新增兩個 mehtod,render
和 controller_name
# mavericks/lib/mavericks/controller.rb
require 'erubi'
module Mavericks
class Controller
attr_reader :env
def initialize(env)
@env = env
end
def render(view_name)
filename = File.join "app", "views", controller_name, "#{view_name}.html.erb"
template = File.read filename
eval(Erubi::Engine.new(template).src)
end
def controller_name
klass = self.class
klass = klass.to_s.gsub /Controller$/, ""
Mavericks.to_underscore klass
end
end
end
我們先來看看 render
這個程式碼,還記得 Rails 的 Controller 最後都會做 render
嗎?沒印象的話,那是因為如果沒有在 Action 裡面寫上 render
, Rails 會自動做預設判斷,而在 render
這個 method 裡面,我們會接收到一個 view_name 的變數,透過預設的檔案路徑去尋找 template,如下面程式碼所示
filename = File.join "app", "views", controller_name, "#{view_name}.html.erb"
這個路徑位置跟 Rails 設計的一樣,也是昨天一直提到的 convention over configuration
模式,我們希望開發者將檔案放在 app/views/ controller_name/action.html.erb
,至於檔案名稱如何處理,像是 Controller 的檔名,這些在之前製作 Controller 已經做過就不再多做說明
接著將讀取到的檔案丟給 erubi
做處理
eval(Erubi::Engine.new(template).src)
然後我們回到 just_do 修改我們的檔案,我們將原本只能回傳字串的 Taskks#index 換成 render
# just_do/app/controllers/tasks_controller.rb
class TasksController < Mavericks::Controller
def index
@task_name = '向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List!'
render 'index'
end
end
接著新增 index.html.erb
template 在相對應的目錄位置
# just_do/app/views/tasks/index.html.erb
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<h1>任務的index</h1>
<%= @task_name %>
</body>
</html>
最後重整後打開網頁 http://127.0.0.1:3001/tasks/index
任務的index
向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List!
應該會看到畫面上出現上列文字,酷!
剛剛我們有提到,在 Rails 裡面如果沒有寫 render
的話,預設是會抓到對應的檔案,例如像剛剛的 TasksController#index
,就會自動去抓app/views/tasks/index.html.erb
,做法不會太難,但我們要先釐清一些觀念,在 Mavericks 中,每一頁 Controller 都是一個物件,所以我們可以加上一個 attribute,來記錄這個 Controller 有沒有執行過 render
# mavericks/lib/mavericks/controller.rb
require 'erubi'
module Mavericks
class Controller
# 加上 attribute
attr_reader :env, :called_render
def initialize(env)
@env = env
# 預設為 false
@called_render = false
end
def render(view_name)
# 執行過改為 true
@called_render = true
filename = File.join "app", "views", controller_name, "#{view_name}.html.erb"
template = File.read filename
eval(Erubi::Engine.new(template).src)
end
def controller_name
klass = self.class
klass = klass.to_s.gsub /Controller$/, ""
Mavericks.to_underscore klass
end
end
end
接著在 Application#call 修改程式碼為
# mavericks/lib/mavericks.rb
def call(env)
return favicon if env["PATH_INFO"] == '/favicon.ico'
return index(env) if env["PATH_INFO"] == '/'
begin
klass, act = get_controller_and_action(env)
controller = klass.new(env)
text = controller.send(act)
# 如果沒有執行過 render,就執行 default_render
text = default_render(controller, act) unless controller.called_render
[200, {'Content-Type' => 'text/html'},
[text]]
rescue
[404, {'Content-Type' => 'text/html'},
['This is a 404 page!!']]
end
end
最後在底下的加上 default_render
# mavericks/lib/mavericks.rb
private
def default_render(controller, act)
controller.render(act)
end
然後回到 just_do 把 render 'index'
拿掉後重整看看,如果畫面沒有出錯,代表我們有 default render
了
功能是越來越完整了,但是這個 View 好像少了點什麼,我記得 Rails 不是蠻多 helper 可以用的嗎?例如: link_to
之類的
嗯...,聽起來應該要加點方便開發者使用的 method 才對,那明天就來加上去吧!